/**
 * Copyright (C) 2013 DaiKit.com - daikit4gxt module (admin@daikit.com)
 *
 *         Project home : http://code.daikit.com/daikit4gxt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.daikit.daikit4gxt.client.ui.cell;

import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.sencha.gxt.cell.core.client.DisableCell;
import com.sencha.gxt.cell.core.client.FocusableCell;
import com.sencha.gxt.cell.core.client.ResizeCell;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.util.KeyNav;
import com.sencha.gxt.widget.core.client.HasIcon;
import com.sencha.gxt.widget.core.client.event.BeforeSelectEvent;
import com.sencha.gxt.widget.core.client.event.BeforeSelectEvent.BeforeSelectHandler;
import com.sencha.gxt.widget.core.client.event.BeforeSelectEvent.HasBeforeSelectHandlers;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent.HasSelectHandlers;
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;


/**
 * @author tcaselli
 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
 */
public abstract class IconButtonCell extends ResizeCell<String> implements HasBeforeSelectHandlers, HasSelectHandlers, HasIcon,
		FocusableCell, DisableCell
{

	/**
	 * Icon alignment enum.
	 */
	public enum IconAlign
	{
		/**
		 * Icons are aligned to the <b>right</b>.
		 */
		RIGHT,
		/**
		 * Icons are aligned to the <b>bottom</b>.
		 */
		BOTTOM,
		/**
		 * Icons are aligned to the <b>top</b>.
		 */
		TOP,
		/**
		 * Icons are aligned to the <b>left</b>.
		 */
		LEFT;
	}

	protected ImageResource icon;
	protected SafeHtml text = SafeHtmlUtils.EMPTY_SAFE_HTML;
	protected final IconButtonCellAppearance appearance;
	private IconAlign iconAlign = IconAlign.LEFT;
	private boolean handleMouseEvents = true;
	private int minWidth = -1;
	private String tooltip;

	/**
	 * Constructor with null tooltip (so not displayed)
	 * 
	 * @param icon
	 *           the icon to be displayed
	 */
	public IconButtonCell(final ImageResource icon)
	{
		this(icon, null, null);
	}

	/**
	 * Constructor with null tooltip (so not displayed)
	 * 
	 * @param icon
	 *           the icon to be displayed
	 * @param selectHandler
	 *           the select handler
	 */
	public IconButtonCell(final ImageResource icon, final SelectHandler selectHandler)
	{
		this(icon, null, selectHandler);
	}

	/**
	 * Constructor
	 * 
	 * @param icon
	 *           the icon to be displayed
	 * @param tooltip
	 *           not displayed if null, set to null by default
	 * @param selectHandler
	 *           the {@link SelectHandler}
	 */
	public IconButtonCell(final ImageResource icon, final String tooltip, final SelectHandler selectHandler)
	{
		this(new IconButtonCellDefaultAppearance());
		this.setIcon(icon);
		this.setTooltip(tooltip);
		if (selectHandler != null)
		{
			this.addSelectHandler(selectHandler);
		}
	}

	/**
	 * Constructor
	 * 
	 * @param appearance
	 */
	public IconButtonCell(final IconButtonCellAppearance appearance)
	{
		super("click", "keydown", "mousedown", "mouseup", "mouseover", "mouseout", "focus", "blur");
		this.appearance = appearance;
	}

	/**
	 * Default implementation of this interface is
	 * 
	 * @author tcaselli
	 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
	 */
	@SuppressWarnings("javadoc")
	public interface IconButtonCellAppearance
	{
		XElement getImageElement(XElement parent);

		XElement getFocusElement(XElement parent);

		void onFocus(XElement parent, boolean focused, NativeEvent event);

		void onOver(XElement parent, boolean over, NativeEvent event);

		void onPress(XElement parent, boolean click, NativeEvent event);

		void render(IconButtonCell cell, Context context, String value, SafeHtmlBuilder sb);

		void setCellIconVisibilityHandler(CellIconVisiblityHandler cellIconVisiblityHandler);

		CellIconVisiblityHandler getCellIconVisibilityHandler();

	}

	private class UnpushHandler implements NativePreviewHandler
	{

		private final XElement parent;
		private final HandlerRegistration reg;

		public UnpushHandler(final XElement parent)
		{
			this.parent = parent;
			this.reg = Event.addNativePreviewHandler(this);
		}

		@Override
		public void onPreviewNativeEvent(final NativePreviewEvent event)
		{
			if ("mouseup".equals(event.getNativeEvent().getType()))
			{
				// Unregister self.
				reg.removeHandler();

				// Unpush the element.
				appearance.onOver(parent, false, event.getNativeEvent());
				appearance.onPress(parent, false, event.getNativeEvent());
			}
		}
	}

	@Override
	public void disable(final com.google.gwt.cell.client.Cell.Context context, final Element parent)
	{
		appearance.onOver(parent.<XElement> cast(), false, null);
		appearance.onFocus(parent.<XElement> cast(), false, null);
	}

	@Override
	public void enable(final com.google.gwt.cell.client.Cell.Context context, final Element parent)
	{
		appearance.onOver(parent.<XElement> cast(), false, null);
	}

	@Override
	public XElement getFocusElement(final XElement parent)
	{
		return appearance.getFocusElement(parent);
	}

	/**
	 * Get the icon
	 */
	@Override
	public ImageResource getIcon()
	{
		return icon;
	}

	/**
	 * Get the icon
	 * 
	 * @param context
	 *           the context
	 * @return {@link #getIcon()} or use the context
	 */
	protected abstract ImageResource getIcon(final Context context);

	@Override
	public void setIcon(final ImageResource icon)
	{
		this.icon = icon;
	}

	@Override
	public HandlerRegistration addSelectHandler(final SelectHandler handler)
	{
		return addHandler(handler, SelectEvent.getType());
	}

	@Override
	public HandlerRegistration addBeforeSelectHandler(final BeforeSelectHandler handler)
	{
		return addHandler(handler, BeforeSelectEvent.getType());
	}

	@Override
	public void render(final com.google.gwt.cell.client.Cell.Context context, final String value, final SafeHtmlBuilder sb)
	{
		appearance.render(this, context, value, sb);
	}

	@Override
	public boolean redrawOnResize()
	{
		return true;
	}

	@Override
	public void onBrowserEvent(final Cell.Context context, final Element parent, final String value, final NativeEvent event,
			final ValueUpdater<String> valueUpdater)
	{

		// ignore the parent element
		if (isDisableEvents() || !getAppearance().getCellIconVisibilityHandler().isCellIconVisible(context))
		{
			return;
		}
		final Element target = event.getEventTarget().cast();
		if (!parent.getFirstChildElement().isOrHasChild(target))
		{
			return;
		}

		final XElement p = parent.cast();

		final String eventType = event.getType();
		if ("click".equals(eventType))
		{
			onClick(context, p, value, event, valueUpdater);
		}
		else if ("mouseover".equals(eventType))
		{
			onMouseOver(p, event);
		}
		else if ("mouseout".equals(eventType))
		{
			onMouseOut(p, event);
		}
		else if ("mousedown".equals(eventType))
		{
			onMouseDown(p, event);
		}
		else if ("mouseup".equals(eventType))
		{
			onMouseUp(p, event);
		}
		else if ("focus".equals(eventType))
		{
			onFocus(p, event);
		}
		else if ("blur".equals(eventType))
		{
			onBlur(p, event);
		}
		else if ("keydown".equals(eventType))
		{
			if (KeyNav.getKeyEvent() == Event.ONKEYDOWN)
			{
				onNavigationKey(context, parent, value, event, valueUpdater);
			}
		}
		else if ("keypress".equals(eventType))
		{
			if (KeyNav.getKeyEvent() == Event.ONKEYPRESS)
			{
				onNavigationKey(context, parent, value, event, valueUpdater);
			}
		}
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// GETTERS SETTERS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	/**
	 * @return the appearance
	 */
	public IconButtonCellAppearance getAppearance()
	{
		return appearance;
	}

	/**
	 * Returns the icon align.
	 * 
	 * @return the iconAlign
	 */
	public IconAlign getIconAlign()
	{
		return iconAlign;
	}

	/**
	 * Returns the icon align.
	 * 
	 * @return {@link #getIcon()} or use the context
	 */
	protected abstract IconAlign getIconAlign(final Context context);

	/**
	 * Returns the icon's minimum width.
	 * 
	 * @return the minWidth the minimum width
	 */
	public int getMinWidth()
	{
		return minWidth;
	}

	/**
	 * Returns false if mouse over effect is disabled.
	 * 
	 * @return false if mouse effects disabled
	 */
	public boolean getMouseEvents()
	{
		return handleMouseEvents;
	}

	/**
	 * Sets he minimum width for this button (used to give a set of buttons a common width)
	 * 
	 * @param minWidth
	 *           the minimum width
	 */
	public void setMinWidth(final int minWidth)
	{
		this.minWidth = minWidth;
	}

	/**
	 * Get the tooltip. Cannot be changed at runtime.
	 * 
	 * @return the tooltip
	 */
	public String getTooltip()
	{
		return tooltip;
	}

	/**
	 * Get the tooltip.
	 * 
	 * @param context
	 *           the context
	 * @return {@link #getTooltip()} or use the context
	 */
	public abstract String getTooltip(final Context context);

	/**
	 * Set the tooltip. Cannot be changed at runtime.
	 * 
	 * @param tooltip
	 *           the tooltip
	 */
	public void setTooltip(final String tooltip)
	{
		this.tooltip = tooltip;
	}

	/**
	 * Sets the icon alignment (defaults to LEFT).
	 * 
	 * @param iconAlign
	 *           the icon alignment
	 */
	public void setIconAlign(final IconAlign iconAlign)
	{
		this.iconAlign = iconAlign;
	}

	/**
	 * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true).
	 * 
	 * @param handleMouseEvents
	 *           false to disable mouse over changes
	 */
	public void setMouseEvents(final boolean handleMouseEvents)
	{
		this.handleMouseEvents = handleMouseEvents;
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// UI CALLBACKS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	protected void onBlur(final XElement p, final NativeEvent event)
	{
		appearance.onFocus(p, false, event);
	}

	protected void onClick(final Context context, final XElement p, final String value, final NativeEvent event,
			final ValueUpdater<String> valueUpdater)
	{
		if (fireCancellableEvent(context, new BeforeSelectEvent(context)))
		{
			appearance.onOver(p, false, null);
			fireEvent(context, new SelectEvent(context));
		}
	}

	protected void onFocus(final XElement p, final NativeEvent event)
	{
		appearance.onFocus(p, true, event);
	}

	protected void onMouseDown(final XElement parent, final NativeEvent event)
	{
		if (handleMouseEvents)
		{
			final Element target = event.getEventTarget().cast();
			// stop images from being dragged in firefox
			if ("IMG".equals(target.getTagName()))
			{
				event.preventDefault();
			}
			appearance.onPress(parent, true, event);

			new UnpushHandler(parent);
		}
	}

	protected void onMouseOut(final XElement p, final NativeEvent event)
	{
		appearance.onOver(p, false, event);
	}

	protected void onMouseOver(final XElement p, final NativeEvent event)
	{
		appearance.onOver(p, true, event);
	}

	protected void onMouseUp(final XElement p, final NativeEvent event)
	{
		appearance.onPress(p, false, event);
	}

	protected void onNavigationKey(final com.google.gwt.cell.client.Cell.Context context, final Element parent,
			final String value, final NativeEvent event, final ValueUpdater<String> valueUpdater)
	{
		final int key = event.getKeyCode();

		if (!isDisableEvents() && (key == KeyCodes.KEY_ENTER || key == 32))
		{
			onClick(context, parent.<XElement> cast(), value, event, valueUpdater);
		}
	}
}
